<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>飞机大战</title>
    <style>
        /* 样式重置 */
        * {
            padding: 0;
            margin: 0;
        }

        canvas {
            border: 1px solid red;
        }

        #stage {
            width: 480px;
            height: 650px;
            margin: auto;
        }
    </style>
</head>

<body>
    <div id="stage">
        <canvas id="canvas" width="480" height="650"></canvas>
    </div>

    <script>
        // 1.让画布能够绘制图片
        //     1.1找到这个画布
        const canvas = document.querySelector('#canvas')
        //     1.2利用这个画布初始化一个2D的画笔
        const context = canvas.getContext('2d')

        // 定义游戏的状态
        // 开始
        const START = 0
        // 开始时
        // const STARTING = 1
        // 进行时
        const RUNNING = 1
        // 暂停时
        const PAUSE = 2
        // 结束时
        const END = 3
        // 2.加载这张图片 图片加载/初始化 这个过程 是异步的
        const bg = new Image()
        bg.src = 'img/background.png'

        // 初始化一个图片LOGO
        const copyright = new Image()
        copyright.src = "img/start.png"

        // 初始化暂停图片
        // const pause=new Image()
        // pause.src="img/game"

        // 初始化四张飞机大战加载图片
        // const loading_frame=[]

        // 初始化英雄图片
        const hero_frame = { live: [], death: [] }
        hero_frame.live[0] = new Image()
        hero_frame.live[0].src = "img/hero1.png"
        hero_frame.live[1] = new Image()
        hero_frame.live[1].src = "img/hero2.png"
        hero_frame.death[0] = new Image()
        hero_frame.death[0].src = "img/hero_blowup_n1.png"
        hero_frame.death[1] = new Image()
        hero_frame.death[1].src = "img/hero_blowup_n2.png"
        hero_frame.death[2] = new Image()
        hero_frame.death[2].src = "img/hero_blowup_n3.png"
        hero_frame.death[3] = new Image()
        hero_frame.death[3].src = "img/hero_blowup_n4.png"

        // 初始化子弹图
        const b = new Image()
        b.src = "img/bullet1.png"

        // 初始化敌机图
        // 初始化小飞机图
        const e1 = {
            live: [],
            death: []
        }
        e1.live[0] = new Image()
        e1.live[0].src = "img/enemy1.png"
        e1.death[0] = new Image()
        e1.death[0].src = "img/enemy1_down1.png"
        e1.death[1] = new Image()
        e1.death[1].src = "img/enemy1_down2.png"
        e1.death[2] = new Image()
        e1.death[2].src = "img/enemy1_down3.png"
        e1.death[3] = new Image()
        e1.death[3].src = "img/enemy1_down4.png"
        // 初始化大飞机图
        const e2 = {
            live: [],
            death: []
        }
        e2.live[0] = new Image()
        e2.live[0].src = "img/enemy2.png"
        e2.death[0] = new Image()
        e2.death[0].src = "img/enemy2_down1.png"
        e2.death[1] = new Image()
        e2.death[1].src = "img/enemy2_down2.png"
        e2.death[2] = new Image()
        e2.death[2].src = "img/enemy2_down3.png"
        e2.death[3] = new Image()
        e2.death[3].src = "img/enemy2_down4.png"
        // 初始化超大飞机图
        const e3 = {
            live: [],
            death: []
        }
        e3.live[0] = new Image()
        e3.live[0].src = "img/enemy3.png"
        e3.death[0] = new Image()
        e3.death[0].src = "img/enemy3_down1.png"
        e3.death[1] = new Image()
        e3.death[1].src = "img/enemy3_down2.png"
        e3.death[2] = new Image()
        e3.death[2].src = "img/enemy3_down3.png"
        e3.death[3] = new Image()
        e3.death[3].src = "img/enemy3_down4.png"
        e3.death[3] = new Image()
        e3.death[3].src = "img/enemy3_down5.png"
        e3.death[3] = new Image()
        e3.death[3].src = "img/enemy3_down6.png"

        // canvas画布 绘制bg对象的左上角坐标
        // 我们需要初始化两个坐标轴位置来绘制两侧bg
        let x1 = 0
        let y1 = 0
        let x2 = 0
        let y2 = -650

        // ES6 新增的写法 更加接近于后台语言java
        // 实际上和ES5的构造函数没有本质差别
        // function Sky（）{}
        // 静态属性 & 动态方法 两个部分
        // 静态属性 提供到一个叫constructor函数中 构造器函数 每当你new XXX（）总会先调用这个constructor（）函数
        // 初始化一个天空类
        class Sky {
            constructor(config) {
                // 静态属性
                // 静态图片
                this.bg = config.bg
                // 背景宽度
                this.width = config.width
                // 背景高度
                this.height = config.height
                this.x1 = 0
                this.y1 = 0
                this.x2 = 0
                this.y2 = -this.height
                this.speed = config.speed
                this.lastTime = new Date().getTime()
            }
            // 动态方法
            // 判断方法 判断这个时间段天空是否需要移动 y1，y2++
            judge() {
                let currentTime = new Date().getTime()
                if (currentTime - this.lastTime > this.speed) {
                    this.y1++
                    this.y2++
                    this.lastTime = currentTime
                }
                if (this.y2 === 0) {
                    this.y1 = 0
                    this.y2 = -this.height
                }
            }
            // 绘图方法
            paint(context) {
                // image加载的图片对象
                // dX图片开始绘制的左上角的那个横坐标
                // dY图片开始绘制的左上角的那个纵坐标
                // dwidth图片在canvas绘制的宽度(缺省值表示的就是绘制到整张canvas对象中)
                // dheight图片在canvas绘制的高度(缺省值表示的就是绘制到整张canvas对象中)
                context.drawImage(this.bg, this.x1, this.y1++, this.width, this.height)
                context.drawImage(this.bg, this.x2, this.y2++, this.width, this.height)
                if (this.y2 === 0) {
                    this.y1 = 0
                    this.y2 = -this.height
                }
            }
            fn2() {

            }
        }
        // 天空类的配置项
        const SKY = {
            bg: bg,
            width: 480,
            height: 650,
            // 10ms变化一次 数字越大 速度越慢
            speed: 10,
        }

        // 飞机加载界面的配置项
        // const LOADING={
        //     frame:
        // }
        // 初始化一个飞机界面加载类
        // class Loading{}

        // 初始化一个英雄类的配置
        const HERO = {
            frame: hero_frame,
            width: 99,
            height: 124,
            speed: 100,
        }

        // 初始化一个子弹类的配置
        const BULLET = {
            img: b,
            width: 9,
            height: 21,
        }

        //敌机的配置项
        // 小飞机的配置项
        const E1 = { type: 1, width: 57, height: 51, life: 1, score: 1, frame: e1, minSpeed: 20, maxSpeed: 10 }
        // 大飞机配置项
        const E2 = { type: 2, width: 69, height: 95, life: 5, score: 5, frame: e2, minSpeed: 50, maxSpeed: 20 }
        // 超大飞机配置项
        const E3 = { type: 3, width: 169, height: 258, life: 20, score: 20, frame: e3, minSpeed: 100, maxSpeed: 100 }
        // 初始化一个英雄类
        class Hero {
            constructor(config) {
                this.width = config.width
                this.height = config.height
                this.x = (480 - config.width) / 2
                this.y = 650 - config.height
                this.frame = config.frame
                this.frameLiveIndex = 0
                this.frameDeathIndex = 0
                this.lastTime = new Date().getTime()
                this.speed = config.speed
                // 当前展示的图片
                this.img = null
                this.live = true
                // 子弹上次射击的时间
                this.lastShootTime = new Date().getTime()
                // 子弹射击的间隔
                this.shootInterval = 200
                this.bulletList = []
                this.destory = false
            }
            judge() {
                const currentTime = new Date().getTime()
                if (currentTime - this.lastTime > this.speed) {
                    if (this.live) {
                        this.img = this.frame.live[this.frameLiveIndex++ % this.frame.live.length]

                    } else {
                        this.img = this.frame.death[this.frameDeathIndex++]
                        if (this.frameDeathIndex === this.frame.death.length) {
                            this.destory = true
                        }
                    }
                    this.lastTime = currentTime
                }
            }
            paint(context) {
                context.drawImage(this.img, this.x, this.y, this.width, this.height)
            }
            // 英雄可以射击子弹
            shoot() {
                // 获取当前时间
                const currentTime = new Date().getTime()
                if (currentTime - this.lastShootTime > this.shootInterval) {
                    let bullet = new Bullet(BULLET, this.x + this.width / 2 - BULLET.width / 2, this.y - BULLET.height)
                    this.bulletList.push(bullet)
                    bullet.paint(context)
                    this.lastShootTime = currentTime
                }
            }
            collide() {
                this.live = false
            }
        }
        // 初始化一个子弹类
        class Bullet {
            constructor(config, x, y) {
                this.img = config.img
                this.width = config.width
                this.height = config.height
                this.x = x
                this.y = y
                this.destory = false
            }
            move() {
                this.y -= 2
            }
            paint(context) {
                context.drawImage(this.img, this.x, this.y)
            }
            outOfBounds() {
                // 如果返回的是真的话 那么我们应该销毁掉这个子弹
                return this.y < -this.height
            }
            collide() {
                // 让这颗子弹变成可销毁状态
                this.destory = true
            }
        }

        // 初始化一个天空实例
        const sky = new Sky(SKY)

        // 初始化一个飞机界面加载实例
        // const loading=new Loading(config)

        // 初始化一个英雄实例
        let hero = new Hero(HERO)
        // 初始化一个子弹类实例
        const bullet = new Bullet(BULLET)

        // 初始化一个敌机类
        class Enemy {
            constructor(config) {
                //敌机类型
                this.type = config.type
                // 敌机宽度
                this.width = config.width
                // 敌机高度
                this.height = config.height
                // 敌机初始化位置
                this.x = Math.floor(Math.random() * (480 - config.width))
                this.y = -config.height
                // 敌机生命
                this.life = config.life
                // 敌机分数
                this.score = config.score
                // 敌机图片库
                this.frame = config.frame
                // 此时此刻展示的图片
                this.img = this.frame.live[0]
                // 活着的标识
                this.live = true
                // this.minSpeed=config.minSpeed
                // this.maxSpeed=config.maxSpeed
                // 敌机速度
                this.speed = Math.floor(Math.random() * (config.minSpeed - config.maxSpeed + 1)) + config.maxSpeed
                // 最后时间标识 在这个时间段 他是不变化的，但是过了这个时间 就要变化
                this.lastTime = new Date().getTime()
                // 死亡下标
                this.deathIndex = 0
                // 确认销毁
                this.destory = false
            }
            move() {
                const currentTime = new Date().getTime()
                if (currentTime - this.lastTime >= this.speed) {
                    if (this.live) {
                        this.img = this.frame.live[0]
                        this.y++
                    } else {
                        this.img = this.frame.death[this.deathIndex++]
                        if (this.deathIndex === this.frame.death.length) {
                            this.destory = true
                        }
                    }
                    // 修正上一次时间
                    this.lastTime = currentTime
                }
            }
            // 渲染敌机
            paint(context) {
                context.drawImage(this.img, this.x, this.y)
            }
            // 检测敌机是否有撞到其他物体
            // 敌机e
            // 子弹 o
            hit(o) {
                // 其他物体的左边
                let ol = o.x
                // 其他物体的右边
                let or = o.x + o.width
                // 其他物体的顶边
                let ot = o.y
                // 其他物体的底边
                let ob = o.y + o.height
                // 敌机的左边
                let el = this.x
                // 敌机的右边
                let er = this.x + this.width
                // 敌机的顶边
                let et = this.y
                // 敌机的底边
                let eb = this.y + this.height
                // 判断他是否有碰到
                if (ol > er || or < el || ot > eb || ob < et) {
                    // 没碰到
                    return false
                } else {
                    // 碰到了
                    return true
                }
            }
            collide() {
                // 中弹了，生命减少1
                this.life--
                if (this.life === 0) {
                    this.live = false
                    score += this.score
                }
            }
            // 敌机销毁工作
            outOfBounds() {
                if (this.y > 650) {
                    return true
                }
            }
        }

        // state表示游戏的状态 取值必须是以上的五种状态
        let state = START
        // score 分数变量
        let score = 0
        let life = 3

        // 为canvas绑定一个点击事件 且他如果是START状态的时候需要修改成STARTING状态
        canvas.addEventListener("click", () => {
            if (state == START) {
                state = RUNNING
            }
        })
        // 为canvas绑定一个鼠标移动事件 鼠标正好在飞机图片中心
        canvas.addEventListener("mousemove", (e) => {
            let x = e.offsetX
            let y = e.offsetY
            hero.x = x - hero.width / 2
            hero.y = y - hero.height / 2
        })

        // 为canvas绑定一个鼠标离开事件
        canvas.addEventListener("mouseleave", () => {
            if (state === RUNNING) {
                state = PAUSE
            }
        })
        // 为canvas绑定一个鼠标进入事件
        canvas.addEventListener("mouseenter", () => {
            if (state === PAUSE) {
                state = RUNNING
            }
        })

        // 碰撞检测函数
        function checkHit() {
            // 遍历所有的敌机
            for (let i = 0; i < enemies.length; i++) {
                if (enemies[i].hit(hero)) {
                    enemies[i].collide()
                    hero.collide()
                }
                //遍历所有的子弹
                for (let j = 0; j < hero.bulletList.length; j++) {
                    // 用第i的敌机和第j个子弹进行碰撞检测 返回的是一个布尔类型
                    // enemies[i].hit(hero.bulletList[j])
                    // 如果碰到了
                    if (enemies[i].hit(hero.bulletList[j])) {
                        enemies[i].collide()
                        hero.bulletList[j].collide()
                    }
                }
            }
        }
        // 该变量中所有的敌机实例
        let enemies = []
        //敌机产生的速率
        let ENEMY_CREATE_INTERVAL = 800
        let ENEMY_LASTTIME = new Date().getTime()
        function createComponent() {
            const currentTime = new Date().getTime()
            if (currentTime - ENEMY_LASTTIME >= ENEMY_CREATE_INTERVAL) {
                let ran = Math.floor(Math.random() * 100)
                if (ran < 60) {
                    // 产生一架小飞机
                    enemies.push(new Enemy(E1))
                } else if (ran < 90) {
                    // 产生一架大飞机
                    enemies.push(new Enemy(E2))
                } else {
                    // 产生一架超大飞机
                    enemies.push(new Enemy(E3))
                }
                // 更新时间
                ENEMY_LASTTIME = currentTime
            }
        }
        // 全局函数 来判断所有子弹/敌人组件
        function judgeComponent() {
            for (let i = 0; i < hero.bulletList.length; i++) {
                hero.bulletList[i].move()
            }
            for (let i = 0; i < enemies.length; i++) {
                enemies[i].move()
            }
        }
        // 全局函数 来绘制所有子弹/敌人组件
        function paintComponent() {
            for (let i = 0; i < hero.bulletList.length; i++) {
                hero.bulletList[i].paint(context)
            }
            for (let i = 0; i < enemies.length; i++) {
                enemies[i].paint(context)
            }
            // 绘制score&life画板
            context.font = "20px 微软雅黑"
            context.fillStyle = "red"
            context.textAlign = "left"
            context.fillText("score:" + score, 10, 20)
            context.textAlign = "right"
            context.fillText("life:" + life, 480 - 10, 20)
            // 重置代码 画笔重置为黑色左对齐
            context.fillStyle = "black"
            context.textAlign = "left"
        }
        // 全局函数 来销毁所有子弹/敌人组件
        function deleteComponent() {
            if (hero.destory) {
                life--
                hero.destory = false
                if (life === 0) {
                    // 游戏结束
                    state = END
                } else {
                    hero = new Hero(HERO)
                }
            }
            for (let i = 0; i < hero.bulletList.length; i++) {
                if (hero.bulletList[i].outOfBounds() || hero.bulletList[i].destory) {
                    hero.bulletList.splice(i, 1)
                }
            }
            for (let i = 0; i < enemies.length; i++) {
                // 如果敌机处于一种待销毁状态 destory=true
                if (enemies[i].outOfBounds() || enemies[i].destory) {
                    enemies.splice(i, 1)
                }
            }
        }
        bg.addEventListener('load', () => {
            // 会实时的渲染整个canvas对象，当游戏进入到不同的状态的时候会渲染不同的内容
            setInterval(() => {
                switch (state) {
                    case START:
                        console.log("开始了")
                        // 渲染移动天空
                        sky.judge()
                        sky.paint(context)
                        // 渲染飞机大战LOGO
                        // 图片原始宽高属性的获取naturalWidth&naturalHeight
                        let logo_x = (480 - copyright.naturalWidth) / 2
                        let logo_y = (650 - copyright.naturalHeight) / 2
                        context.drawImage(copyright, logo_x, logo_y)
                        break
                    // case STARTING:
                    //     console.log("开始时")
                    //     // 渲染移动天空
                    //     sky.judge()
                    //     sky.paint(context)
                    //     // 渲染加载类
                    //     break
                    case RUNNING:
                        console.log("运行时")
                        // 渲染移动天空
                        sky.judge()
                        sky.paint(context)
                        // 渲染我方飞机
                        hero.judge()
                        hero.paint(context)
                        hero.shoot()
                        judgeComponent()
                        deleteComponent()
                        paintComponent()
                        createComponent()
                        checkHit()
                        break
                    case PAUSE:
                        console.log("暂停时")
                        // 渲染暂停图标
                        context.font = "bold 24px 微软雅黑"
                        context.textAlign = "center"
                        context.textBaseline = "middle"
                        context.fillText("暂停", 480 / 2, 650 / 2)
                        break
                    case END:
                        console.log("结束时")
                        // 渲染GAME_OVER
                        context.font = "bold 24px 微软雅黑"
                        context.textAlign = "center"
                        context.textBaseline = "middle"
                        context.fillText("GAME_OVER", 480 / 2, 650 / 2)
                        break
                }
            }, 10)
        })
    </script>
</body>

</html>